# app.py  ——  直接覆盖原文件
from flask import Flask, jsonify, session, request
from flask_cors import CORS
from routes import register_blueprints
from services.log_collector import LinuxLogCollector, LogCollectScheduler
import yaml
import os, logging, paramiko, threading, json, time
from logging.handlers import RotatingFileHandler
from flask_socketio import SocketIO, emit
from core.detector.detector import SystemMonitor
from core.roothealer.config_manager import config_manager


# -------------------- 日志 --------------------
if not os.path.exists('logs'):
    os.mkdir('logs')
file_handler = RotatingFileHandler('logs/app.log', maxBytes=10240, backupCount=10, encoding='utf-8')
file_handler.setFormatter(logging.Formatter(
    '%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'
))
file_handler.setLevel(logging.INFO)

# -------------------- 创建 App --------------------
app = Flask(__name__)
app.secret_key = os.urandom(24)      # session 用到
CORS(app)

# 日志
app.logger.addHandler(file_handler)
app.logger.setLevel(logging.INFO)
app.logger.info('应用启动')

# SocketIO
socketio = SocketIO(app, cors_allowed_origins="*", logger=False, engineio_logger=False)

# -------------------- 原蓝图 & 静态路由 --------------------
register_blueprints(app)

@app.route('/detection-data/<path:filename>')
def detection_data(filename):
    return app.send_static_file(os.path.join('data/detection_data', filename))

@app.route('/rootheal-data/<path:filename>')
def rootheal_data(filename):
    return app.send_static_file(os.path.join('data/rootheal_data', filename))

@app.route('/detection-data/mysql_data/<path:filename>')
def mysql_detection_data(filename):
    base_path = os.path.join(os.path.dirname(__file__), 'data', 'DefectIdentification_data', 'mysql_data')
    file_path = os.path.join(base_path, filename)
    return app.send_static_file(os.path.join('data', 'DefectIdentification_data', 'mysql_data', filename)) \
        if os.path.exists(file_path) else (jsonify({'error': 'File not found'}), 404)

@app.route('/detection-data/web_data/<path:filename>')
def web_detection_data(filename):
    base_path = os.path.join(os.path.dirname(__file__), 'data', 'DefectIdentification_data', 'web_data')
    file_path = os.path.join(base_path, filename)
    return app.send_static_file(os.path.join('data', 'DefectIdentification_data', 'web_data', filename)) \
        if os.path.exists(file_path) else (jsonify({'error': 'File not found'}), 404)

# -------------------- 监控启停 --------------------
monitor = None
monitor_thread = None
log_collector = LinuxLogCollector()
log_scheduler = None

@app.route('/api/start-monitoring')
def start_monitoring():
    global monitor, monitor_thread
    if monitor is None:
        monitor = SystemMonitor()
    if monitor_thread is None or not monitor_thread.is_alive():
        monitor_thread = threading.Thread(target=monitor.start_monitoring, args=(5, True))
        monitor_thread.daemon = True
        monitor_thread.start()
        return jsonify({'status': 'success', 'message': '监控已启动，自动故障分析功能已启用'})
    return jsonify({'status': 'warning', 'message': '监控已在运行'})

@app.route('/api/stop-monitoring')
def stop_monitoring():
    return jsonify({'status': 'info', 'message': '监控停止功能需要在SystemMonitor中实现'})

@app.route('/api/trigger-fault-analysis', methods=['POST'])
def trigger_fault_analysis():
    """手动触发故障分析（用于测试）"""
    global monitor
    if monitor is None:
        return jsonify({'status': 'error', 'message': '监控器未初始化'})
    
    if not monitor.auto_fault_analyzer:
        return jsonify({'status': 'error', 'message': '自动故障分析器未初始化'})
    
    try:
        # 模拟异常数据用于测试
        test_anomalies = [
            {
                'type': 'cpu_usage',
                'message': 'CPU使用率过高: 95%',
                'level': 'critical',
                'value': 95.0,
                'threshold': 80.0
            }
        ]
        
        test_metrics = {
            'cpu': {'usage_percent': 95.0},
            'memory': {'usage_percent': 75.0},
            'disk': {'usage_percent': 60.0},
            'network': {'status': 'up'},
            'npu': {'available': False}
        }
        
        report_id = f"manual_test_{int(time.time())}"
        result = monitor.auto_fault_analyzer.analyze_anomalies(
            test_anomalies, test_metrics, 3, report_id
        )
        
        if result:
            return jsonify({
                'status': 'success', 
                'message': '故障分析完成',
                'report_path': result
            })
        else:
            return jsonify({'status': 'warning', 'message': '故障分析未生成报告'})
            
    except Exception as e:
        app.logger.error(f'手动触发故障分析失败: {e}')
        return jsonify({'status': 'error', 'message': f'分析失败: {str(e)}'})

@app.route('/api/fault-analysis-status')
def fault_analysis_status():
    """获取自动故障分析状态"""
    global monitor
    if monitor is None:
        return jsonify({'status': 'error', 'message': '监控器未初始化'})
    
    status = {
        'monitor_initialized': monitor is not None,
        'auto_fault_analyzer_initialized': monitor.auto_fault_analyzer is not None,
        'monitoring_active': monitor_thread is not None and monitor_thread.is_alive() if monitor_thread else False
    }
    
    if monitor.auto_fault_analyzer:
        status.update({
            'fault_matcher_ready': monitor.auto_fault_analyzer.matcher is not None,
            'neo4j_handler_ready': monitor.auto_fault_analyzer.neo4j is not None,
            'graphrag_ready': monitor.auto_fault_analyzer.rag_retriever is not None
        })
    
    return jsonify({'status': 'success', 'data': status})

# -------------------- 配置管理 API --------------------
@app.route('/api/config', methods=['GET'])
def get_config():
    """获取当前AI配置"""
    try:
        config = config_manager._config
        if not config:
            return jsonify({'status': 'error', 'message': '配置加载失败'}), 500
        
        return jsonify({
            'status': 'success',
            'data': config
        })
    except Exception as e:
        app.logger.error(f'获取配置失败: {e}')
        return jsonify({'status': 'error', 'message': f'获取配置失败: {str(e)}'}), 500

@app.route('/api/config', methods=['POST'])
def save_config():
    """保存AI配置"""
    try:
        new_config = request.get_json()
        if not new_config:
            return jsonify({'status': 'error', 'message': '配置数据为空'}), 400
        
        # 验证配置格式
        required_fields = ['providers', 'current_provider']
        for field in required_fields:
            if field not in new_config:
                return jsonify({'status': 'error', 'message': f'缺少必需字段: {field}'}), 400
        
        # 保存到配置文件
        config_path = config_manager.config_path
        with open(config_path, 'w', encoding='utf-8') as f:
            json.dump(new_config, f, ensure_ascii=False, indent=2)
        
        # 重新加载配置
        config_manager.reload_config()
        
        app.logger.info(f'配置已保存: {config_path}')
        return jsonify({
            'status': 'success',
            'message': '配置保存成功'
        })
        
    except Exception as e:
        app.logger.error(f'保存配置失败: {e}')
        return jsonify({'status': 'error', 'message': f'保存配置失败: {str(e)}'}), 500

@app.route('/api/config/reload', methods=['POST'])
def reload_config():
    """重新加载配置"""
    try:
        config_manager.reload_config()
        app.logger.info('配置已重新加载')
        return jsonify({
            'status': 'success',
            'message': '配置重新加载成功'
        })
    except Exception as e:
        app.logger.error(f'重新加载配置失败: {e}')
        return jsonify({'status': 'error', 'message': f'重新加载配置失败: {str(e)}'}), 500

@app.route('/api/config/test', methods=['POST'])
def test_config():
    """测试配置连接"""
    try:
        test_config = request.get_json()
        if not test_config:
            return jsonify({'status': 'error', 'message': '配置数据为空'}), 400
        
        # 获取当前提供商配置
        current_provider = test_config.get('current_provider', 'local')
        providers = test_config.get('providers', {})
        provider_config = providers.get(current_provider, {})
        
        if not provider_config:
            return jsonify({'status': 'error', 'message': f'未找到提供商配置: {current_provider}'}), 400
        
        # 构建测试请求
        api_url = provider_config.get('api_url', '')
        model = provider_config.get('model', '')
        api_key = provider_config.get('api_key', '')
        
        if not api_url or not model:
            return jsonify({'status': 'error', 'message': 'API地址或模型名称不能为空'}), 400
        
        # 构建请求头
        headers = {
            'Content-Type': 'application/json'
        }
        if api_key:
            headers['Authorization'] = f'Bearer {api_key}'
        
        # 构建测试请求数据
        chat_endpoint = provider_config.get('chat_endpoint', '/v1/chat/completions')
        test_url = api_url.rstrip('/') + chat_endpoint
        
        test_data = {
            'model': model,
            'messages': [
                {'role': 'user', 'content': 'Hello, this is a test message.'}
            ],
            'max_tokens': 10,
            'temperature': 0.1
        }
        
        # 发送测试请求
        import requests
        response = requests.post(
            test_url,
            headers=headers,
            json=test_data,
            timeout=10
        )
        
        if response.status_code == 200:
            return jsonify({
                'status': 'success',
                'message': '配置测试成功',
                'provider': current_provider,
                'model': model
            })
        else:
            return jsonify({
                'status': 'error',
                'message': f'配置测试失败: HTTP {response.status_code}',
                'details': response.text[:200]
            }), 400
            
    except Exception as e:
        app.logger.error(f'测试配置失败: {e}')
        return jsonify({'status': 'error', 'message': f'测试配置失败: {str(e)}'}), 500

# -------------------- WebSocket 终端 --------------------
@socketio.on('connect', namespace='/ws/ssh')
def ssh_connect():
    app.logger.info('前端终端已连接')

@socketio.on('start', namespace='/ws/ssh')
def ssh_start(data):
    try:
        host = data['host']
        port = int(data.get('port', 22))
        username, password = data['username'], data['password']

        ssh = paramiko.SSHClient()
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        ssh.connect(hostname=host, port=port, username=username, password=password)
        chan = ssh.invoke_shell(term='xterm', environment={'LANG': 'en_US.UTF-8'})
        chan.settimeout(0)

        # 发送一个换行符来触发shell提示符
        chan.send('\n')

        # 直接使用request.sid而不是存储在session中
        sid = request.sid
        session[f'ssh_{sid}'] = (ssh, chan)

        def pump():
            # 创建应用上下文
            with app.app_context():
                while True:
                    if chan.recv_ready():
                        out = chan.recv(65535).decode(errors='ignore')
                        emit('data', out, room=sid, namespace='/ws/ssh')
                    else:
                        socketio.sleep(0.01)

        socketio.start_background_task(pump)

        @socketio.on('input', namespace='/ws/ssh')
        def on_input(msg):
            chan.send(msg)

    except Exception as e:
        emit('data', f'\r\nSSH Error: {e}\r\n', room=request.sid, namespace='/ws/ssh')

@socketio.on('disconnect', namespace='/ws/ssh')
def ssh_disconnect():
    # 直接使用request.sid而不是从session中获取
    sid = request.sid
    ssh, chan = session.pop(f'ssh_{sid}', (None, None))
    if chan: chan.close()
    if ssh:  ssh.close()

# -------------------- 启动 --------------------
if __name__ == '__main__':
    if monitor is None:
        monitor = SystemMonitor()
    if monitor_thread is None or not monitor_thread.is_alive():
        monitor_thread = threading.Thread(target=monitor.start_monitoring, args=(5, True))
        monitor_thread.daemon = True
        monitor_thread.start()
        app.logger.info('监控已自动启动')

    try:
        # 启动时立即采集一次，并启动周期任务（可调整间隔）
        app.logger.info('启动日志采集...')
        # 从配置读取默认参数
        defaults = {}
        try:
            conf_path = os.path.join(os.path.dirname(__file__), 'conf.yaml')
            if os.path.exists(conf_path):
                with open(conf_path, 'r', encoding='utf-8', errors='ignore') as f:
                    conf = yaml.safe_load(f) or {}
                defaults = conf.get('log_collection', {}) or {}
        except Exception:
            defaults = {}

        min_level = defaults.get('min_level', 'WARN')
        since_hours = defaults.get('since_hours', 24)
        max_lines_per_source = defaults.get('max_lines_per_source', 10000)
        max_total_lines = defaults.get('max_total_lines', 50000)
        incremental = defaults.get('incremental', True)
        save_raw = defaults.get('save_raw', False)

        # 先基于 backend/log.txt 清单采集每源最新10行
        try:
            app.logger.info('基于 backend/log.txt 进行清单采集(每源10行)')
            log_collector.collect_from_path_list(limit_per_source=10, min_level=min_level, save_raw=save_raw)
        except Exception as e:
            app.logger.warning(f'清单采集失败: {e}')

        log_collector.collect_local(
            min_level=min_level,
            since_hours=since_hours,
            max_lines_per_source=max_lines_per_source,
            max_total_lines=max_total_lines,
            incremental=incremental,
            save_raw=save_raw,
        )
        log_scheduler = LogCollectScheduler(
            log_collector,
            interval_seconds=300,
            min_level=min_level,
            since_hours=since_hours,
            max_lines_per_source=max_lines_per_source,
            save_raw=save_raw,
        )
        log_scheduler.start()
        app.logger.info('日志采集已启动')
    except Exception as e:
        app.logger.error(f'日志采集启动失败: {e}')

    socketio.run(app, debug=True, port=5000, host='0.0.0.0', use_reloader=False)

